using DapperExtensionsNew;
using DapperExtensionsNew.Configuration;
using DapperExtensionsNew.Data;
using DapperExtensionsNew.Data.Descriptors;
using DapperExtensionsNew.Queryable;
using DapperExtensionsNew.Utils;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DapperExtensionsNew.Builders
{
    public class SqlBuilder : ISqlBuilder
	{
		private static readonly ConcurrentDictionary<Type, Delegate> QueryHandlers = new ConcurrentDictionary<Type, Delegate>();

		private Compiler _compiler;

		public static readonly SqlBuilder Instance = new SqlBuilder();

		private Compiler QueryCompiler => _compiler ?? (_compiler = new Compiler());

		private SqlBuilder()
		{
		}

		private Clause GetSqlClause<TModel>(IQuery<TModel> query)
		{
			return ((Func<IQuery<TModel>, Clause>)QueryHandlers.GetOrAdd(query.GetType(), (Type n) => QueryCompiler.Compile<TModel>(n) ?? throw new ArgumentException("QueryCompiler " + n.FullName + " Get Null Delegate")))(query);
		}

		private string BuildPageSql<TModel>(IQuery<TModel> query, Clause clause)
		{
			TableDescriptor tableDescriptor = TableCache.GetTableDescriptor(typeof(TModel));
			clause.Paging = true;
			SqlDatabaseOptions options = tableDescriptor.Options;
			if (query.OrderBys == null || query.OrderBys.Count == 0)
			{
				BuildDefaultOrderBy(clause, tableDescriptor);
			}
			StringBuilder stringBuilder = StringBuilderCache.Acquire();
			stringBuilder.Append(BuildSelectSql(tableDescriptor));
			stringBuilder.Append(clause.Where);
			stringBuilder.Append(clause.OrderBy);
			stringBuilder.Append(options.GetPageSql(query.Skip.GetValueOrDefault(), query.Take ?? 1));
			stringBuilder.Append(" ;");
			stringBuilder.Append("SELECT COUNT(1) from ");
			stringBuilder.Append(options.StartDelimiter);
			stringBuilder.Append(tableDescriptor.TableName);
			stringBuilder.Append(options.EndDelimiter);
			stringBuilder.Append(clause.Where);
			stringBuilder.Append(" ;");
			return StringBuilderCache.GetStringAndRelease(stringBuilder);
		}

		private static void BuildDefaultOrderBy(Clause clause, TableDescriptor descriptor)
		{
			SqlDatabaseOptions options = descriptor.Options;
			List<ColumnDescriptor> list = descriptor.ColumnDescriptors.Where((ColumnDescriptor n) => n.IsPrimaryKey).ToList();
			StringBuilder stringBuilder = StringBuilderCache.Acquire();
			int count = list.Count;
			stringBuilder.Append(" ORDER BY");
			for (int i = 0; i < count; i++)
			{
				ColumnDescriptor columnDescriptor = list[i];
				stringBuilder.Append(" ");
				stringBuilder.Append(options.StartDelimiter);
				stringBuilder.Append(columnDescriptor.DbName);
				stringBuilder.Append(options.EndDelimiter);
				stringBuilder.Append(" ");
				stringBuilder.Append(OrderDirection.Desc);
				if (i != count - 1)
				{
					stringBuilder.Append(",");
				}
			}
			clause.OrderBy = StringBuilderCache.GetStringAndRelease(stringBuilder);
		}

		public Clause SelectAsync<TModel>(IQuery<TModel> query)
		{
			Clause sqlClause = GetSqlClause(query);
			try
			{
				if (query.Skip.HasValue || query.Take.HasValue)
				{
					string text2 = sqlClause.Sql = BuildPageSql(query, sqlClause);
					return sqlClause;
				}
				if (string.IsNullOrEmpty(sqlClause.Where))
				{
					return sqlClause;
				}
				TableDescriptor tableDescriptor = TableCache.GetTableDescriptor(typeof(TModel));
				StringBuilder stringBuilder = StringBuilderCache.Acquire();
				stringBuilder.Append(BuildSelectSql(tableDescriptor));
				stringBuilder.Append(sqlClause.Where);
				stringBuilder.Append(sqlClause.OrderBy);
				sqlClause.Sql = StringBuilderCache.GetStringAndRelease(stringBuilder);
				return sqlClause;
			}
			finally
			{
				sqlClause.Where = null;
				sqlClause.OrderBy = null;
			}
		}

		private static string GetColumns(TableDescriptor descriptor)
		{
			SqlDatabaseOptions options = descriptor.Options;
			string startDelimiter = options.StartDelimiter;
			string endDelimiter = options.EndDelimiter;
			int count = descriptor.ColumnDescriptors.Count;
			StringBuilder stringBuilder = StringBuilderCache.Acquire();
			for (int i = 0; i < count; i++)
			{
				ColumnDescriptor columnDescriptor = descriptor.ColumnDescriptors[i];
				stringBuilder.Append(" ");
				stringBuilder.Append(startDelimiter);
				stringBuilder.Append(columnDescriptor.DbName);
				stringBuilder.Append(endDelimiter);
				stringBuilder.Append(" As ");
				stringBuilder.Append(startDelimiter);
				stringBuilder.Append(columnDescriptor.Name);
				stringBuilder.Append(endDelimiter);
				stringBuilder.Append(" ");
				if (i != count - 1)
				{
					stringBuilder.Append(",");
				}
			}
			return StringBuilderCache.GetStringAndRelease(stringBuilder);
		}

		private string BuildSelectSql(TableDescriptor descriptor)
		{
			string columns = GetColumns(descriptor);
			string tableName = descriptor.TableName;
			return descriptor.Options.GetSelectSql(columns, tableName);
		}
	}
}
